using System;
using System.Collections.Generic;
using CLELCore;
using CLELCore.Assembly;
using CLELCore.Assembly.CLELInstructions;
using CLELCore.Assembly.Descriptors;
using CLELCore.Assembly.StreamTables;

namespace opt.Analysis.Graph.CallGraph
{
	public class CallGraph<T,U> : Graph<T,U> where U : NodeInfo,new()
						where T : ArcInfo,new()
	{
		private List<MethodDescriptor> _method_descriptors;
		private CLEL _clel;

		public CallGraph(List<MethodDescriptor> methods,CLEL clel)
		{
			if(methods == null)
				throw new ArgumentException("Methods list cannot be null");
			if(clel == null)
				throw new ArgumentException("CLEL cannot be null");
			_method_descriptors = methods;
			_clel = clel;
			BuildCallGraph();
		}

		private void BuildCallGraph()
		{
			Dictionary<Token,Boolean> graph_tokens = new Dictionary<Token,Boolean>();
			Dictionary<Token,GraphNode<T,U>> seen = new Dictionary<Token,GraphNode<T,U>>();
			Dictionary<Token,GraphNode<T,U>> roots = new Dictionary<Token,GraphNode<T,U>>();

			MethodDescriptor from_md,to_md;
			Token from_token,to_token;
			GraphNode<T,U> from,to;
			List<CLELInstruction> code;
			int i,methods_count = _method_descriptors.Count;
			int j,code_count;
			byte opcode;
			Call call;
			Calli calli;
			Callvirt callvirt;
			Jmp jmp;
			CLELInstruction inst;
			T t;
			U u,v;
			for(i = 0;i < methods_count;i++)
			{
				graph_tokens.Add(_method_descriptors[i].MethodToken,true);
			}
			for(i = 0;i < methods_count;i++)
			{
				from_md = _method_descriptors[i];
				from_token = from_md.MethodToken;
				code = from_md.GetMethodsCode();
				if(seen.ContainsKey(from_token))
					from = seen[from_token];
				else
				{
					u = new U();
					u.MethodToken = from_token;
					u.LoadInfo(_clel,from_md);
					from = new GraphNode<T,U>(u);
					seen.Add(from_token,from);
					roots.Add(from_token,from);
				}
				code_count = 0;
				if(code != null)
					code_count = code.Count;
				//code_count = code.Count;
				for(j = 0;j < code_count;j++)
				{
					inst = code[j];
					opcode = inst.Opcode;
					if(opcode == CLELOpcode.CALL || opcode == CLELOpcode.CALLI
						|| opcode == CLELOpcode.CALLVIRT || opcode == CLELOpcode.JMP)
					{
						if(opcode == CLELOpcode.CALL)
						{
							call = (Call)inst;
							to_token = call.Token;
						}
						else if(opcode == CLELOpcode.CALLI)
						{
							calli = (Calli)inst;
							to_token = calli.Token;
						}
						else if(opcode == CLELOpcode.CALLVIRT)
						{
							callvirt = (Callvirt)inst;
							to_token = callvirt.Token;
						}
						else
						{
							jmp = (Jmp)inst;
							to_token = jmp.Token;
						}
						if(to_token.Table == StreamTable.METHODDEF_TABLE && graph_tokens.ContainsKey(to_token))
						{
							to_md = _clel.GetMethodDescriptor(to_token);
							if(seen.ContainsKey(to_token))
							{
								to = seen[to_token];
								if(roots.ContainsKey(to_token))
								{
									roots.Remove(to_token);
								}
							}
							else
							{
								v = new U();
								v.MethodToken = to_token;
								v.LoadInfo(_clel,to_md);
								to = new GraphNode<T,U>(v);
								seen.Add(to_token,to);
							}
							t = new T();
							t.CallIndex = j;
							t.LoadInfo(_clel,from_md,to_md);
							from.AddGraphArc(new GraphArc<T,U>(to,t));
							to.AddBackGraphArc(new GraphArc<T,U>(from,null));
						}
					}
				}
			}
			
			foreach(KeyValuePair<Token,GraphNode<T,U>> kvp in roots)
			{
				_graph_roots.Add(kvp.Value);
			}
		}
	}
}
